﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HgCo.WindowsLive.SkyDrive;
using System.Collections.Specialized;
using HtmlAgilityPack;
using System.IO;
using System.Threading;

namespace Xin.RenRen.PhotoSetDownloader
{
    public class ProgressEventArgs : EventArgs
    {
        public int Total { get; set; }
        public int Downloaded { get; set; }
    }

    public class RenRen
    {
        public class Constants
        {
            public const string LoginUrl = "http://passport.renren.com/PLogin.do";
            public const string LoginParamUserName = "email";
            public const string LoginParamPassword = "password";
            public const string PhotoSetIDStart = "/getalbum.do?id=";
            public const string PhotoPageUrlTemplate = "/getalbum.do?curpage={0}&id={1}&owner={2}&t=";
            public const string PhotoSetIDEnd = "&";
            public const string PhotoSetBaseUrl = "http://photo.renren.com";
            public const string PhotoSetPagerStart = "/getalbum.do?curpage=";
            public const string PhotoSetPagerEnd = "\"";
            public const string ClassPhotoItem = "img";
            public const string ClassPagerTop = "pager-top";
            public const string IDSinglePhoto = "photo";
            public const string IDTotalCount = "summary";
            public const int CountPerPage = 15;
        }

        public class PageJob
        {
            public int pageIdx;
            public string id;
            public string owner;
            public string saveDir;
        }

        public event Action<string> TraceInfo;

        public event EventHandler<ProgressEventArgs> Progress;

        public event EventHandler<ProgressEventArgs> PageProgress;

        private void ReportProgress(int downloaded, int total)
        {
            if (Progress != null)
            {
                Progress(this, new ProgressEventArgs { Downloaded = downloaded, Total = total });
            }
        }

        private void ReportPageProgress(int downloaded, int total)
        {
            if (PageProgress != null)
            {
                PageProgress(this, new ProgressEventArgs { Downloaded = downloaded, Total = total });
            }
        }

        private void Trace(string info)
        {
            if (TraceInfo != null)
            {
                TraceInfo(info);
            }
        }

        private void Trace(string format, params object[] param)
        {
            if (TraceInfo != null)
            {
                TraceInfo(string.Format(format, param));
            }
        }

        public const int MaxRetry = 3;

        public string UserName { get; set; }
        public string Password { get; set; }

        private int photoCount = 0;
        private int downloaded = 0;
        private int pageCount = 0;
        private int downloadPage = 0;

        public HttpWebClient _webClient;
        public WebSession _webSession;
        private bool _shouldStop = false;

        private static List<RenRen> _renrenClients = new List<RenRen>();

        public static void StopAll()
        {
            foreach (var client in _renrenClients)
            {
                client.Stop();
            }
        }

        private void Stop()
        {
            _shouldStop = true;
        }

        public RenRen(string userName, string password)
        {
            this.UserName = userName;
            this.Password = password;
            _renrenClients.Add(this);
            LogOn();
        }

        private void LogOn()
        {
            _webSession = new WebSession();
            _webClient = new HttpWebClient(_webSession);
            _webClient.Timeout = 5000;
            var logonParams = new NameValueCollection();
            logonParams[Constants.LoginParamUserName] = UserName;
            logonParams[Constants.LoginParamPassword] = Password;
            var responseString = _webClient.UploadValuesUrlEncoded(new Uri(Constants.LoginUrl), logonParams);
        }

        public void DownloadPhotoSet(string photoSetUrl, string saveDir)
        {
            try
            {
                var idIdx = photoSetUrl.IndexOf("id=");
                var ownerIdx = photoSetUrl.IndexOf("owner=");
                string id = null;
                string owner = null;
                var idxVar = 0;
                var idxEnd = 0;
                if (idIdx < ownerIdx)
                {
                    idxVar = photoSetUrl.IndexOf('&', idIdx);
                    id = photoSetUrl.Substring(idIdx + 3, idxVar - idIdx - 3);
                    idxEnd = photoSetUrl.IndexOf('&', idxVar + 1);
                    if (idxEnd == -1)
                        owner = photoSetUrl.Substring(ownerIdx + 6);
                    else
                        owner = photoSetUrl.Substring(ownerIdx + 6, idxEnd - ownerIdx - 6);
                }
                else
                {
                    idxVar = photoSetUrl.IndexOf('&', ownerIdx);
                    owner = photoSetUrl.Substring(ownerIdx + 6, idxVar - ownerIdx - 6);
                    idxEnd = photoSetUrl.IndexOf('&', idxVar + 1);
                    if (idxEnd == -1)
                        id = photoSetUrl.Substring(idIdx + 3);
                    else
                        id = photoSetUrl.Substring(idIdx + 3, idxEnd - idIdx - 3);
                }

                var doc = new HtmlDocument();
                var docHtml = _webClient.DownloadString(new Uri(photoSetUrl));
                doc.LoadHtml(docHtml);
                var title = doc.DocumentNode.SelectNodes(".//title")[0].InnerText;
                Trace("开始下载相册{0}", title);
                try
                {
                    photoCount = int.Parse(doc.GetElementbyId(Constants.IDTotalCount).InnerText);
                }
                catch
                {
                    var pagerTop = doc.DocumentNode.SelectNodes(".//div[@class='pager-top']")[0].InnerHtml;
                    var idxGong = pagerTop.IndexOf("共");
                    var idxZhang = pagerTop.IndexOf("张", idxGong);
                    photoCount = int.Parse(pagerTop.Substring(idxGong + 1, idxZhang - idxGong - 1).Trim());
                }
                var pVar = photoCount / Constants.CountPerPage;
                pageCount = photoCount % Constants.CountPerPage == 0 ? pVar : pVar + 1;
                Trace("找到{0}页", pageCount);
                ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object userState)
                    {
                        saveDir = EnsureDirectory(saveDir, title);
                        for (int i = 0; i < pageCount; i++)
                        {
                            if (_shouldStop)
                            {
                                return;
                            }
                            var pagejob = new PageJob
                            {
                                pageIdx = i,
                                saveDir = saveDir,
                                id = id,
                                owner = owner,
                            };
                            ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object ctx)
                            {
                                var job = (PageJob)ctx;
                                DownloadPhotoSetPage(job.pageIdx, job.id, job.owner, job.saveDir);
                            }), pagejob);
                        }
                    }));
            }
            catch (Exception ex)
            {
                Trace("下载失败，错误信息：" + ex.Message);
            }
        }

        private void DownloadPhotoSetPage(int pageIdx, string id, string owner, string saveDir)
        {
            try
            {
                var pageUrl = string.Format(Constants.PhotoPageUrlTemplate, pageIdx, id, owner);
                var doc = new HtmlDocument();
                var docHtml = _webClient.DownloadString(new Uri(Constants.PhotoSetBaseUrl + pageUrl));
                doc.LoadHtml(docHtml);
                var photoThumbs = doc.DocumentNode.SelectNodes(string.Format(".//span[@class='{0}']", Constants.ClassPhotoItem));
                Trace("开始下载第{0}页：{1}", pageIdx + 1, pageUrl);
                foreach (var thumbNode in photoThumbs)
                {
                    if (_shouldStop)
                    {
                        return;
                    }
                    var singlePhotoUrl = thumbNode.SelectSingleNode(".//a").Attributes["href"].Value;
                    DownloadSinglePhoto(pageIdx, singlePhotoUrl, saveDir);
                }
                downloadPage++;
            }
            catch (Exception ex)
            {
                Trace("下载第{0}页失败，错误信息：" + ex.Message, pageIdx + 1);
            }
            ReportPageProgress(downloadPage, pageCount);
        }

        private void DownloadSinglePhoto(int pageIdx, string singlePhotoUrl, string saveDir)
        {
            var pageHtml = _webClient.DownloadString(new Uri(singlePhotoUrl));
            var pageDoc = new HtmlDocument();
            pageDoc.LoadHtml(pageHtml);
            var imgRealUrl = pageDoc.GetElementbyId(Constants.IDSinglePhoto).Attributes["src"].Value;
            var fileName = pageIdx + "_" + Path.GetFileName(imgRealUrl);
            if (!Directory.GetFiles(saveDir).Select(f => Path.GetFileName(f)).Contains(fileName))
            {
                Trace("开始下载单张照片 {0}", fileName);
                var retry = 0;
                while (retry++ < MaxRetry)
                {
                    try
                    {
                        if (_shouldStop)
                        {
                            return;
                        }
                        var fileData = _webClient.DownloadData(new Uri(imgRealUrl));
                        File.WriteAllBytes(Path.Combine(saveDir, fileName), fileData);
                        OneDownloaded();
                        return;
                    }
                    catch { }
                }
                Trace("下载单张照片失败 {0}", fileName);
            }
            else
            {
                Trace("存在同名文件，跳过 {0}", fileName);
                OneDownloaded();
            }
        }

        private void OneDownloaded()
        {
            downloaded++;
            ReportProgress(downloaded, photoCount);
        }

        private string EnsureDirectory(string saveDir, string title)
        {
            var newSaveDir = Path.Combine(saveDir, title);
            if (!Directory.Exists(newSaveDir))
            {
                try
                {
                    Directory.CreateDirectory(newSaveDir);
                }
                catch
                {
                    var sb = new StringBuilder();
                    foreach (var c in title)
                    {
                        var cate = Char.GetUnicodeCategory(c);
                        if (cate != System.Globalization.UnicodeCategory.OtherSymbol
                            && c != ':')
                            sb.Append(c);
                    }
                    newSaveDir = Path.Combine(saveDir, sb.ToString());
                    Directory.CreateDirectory(newSaveDir);
                }
            }
            return newSaveDir;
        }

        private string[] GetPageUrls(HtmlDocument doc, string docHtml)
        {
            var pageUrls = new Dictionary<string, bool>();
            var startIdx = -1;
            var endIdx = 0;
            while ((startIdx = docHtml.IndexOf(Constants.PhotoSetPagerStart, endIdx)) != -1)
            {
                endIdx = docHtml.IndexOf(Constants.PhotoSetPagerEnd, startIdx);
                pageUrls[docHtml.Substring(startIdx, endIdx - startIdx)] = true;
            }
            return pageUrls.Keys.ToArray();
        }
    }
}
